'use strict';

const fs = require('fs')
const cluster = require('cluster')
const os = require('os')
const { spawn } = require('child_process')

const bodyParser = require('./bodyparser')
const middleware1 = require('./middleware1')
const middleware2 = require('./middleware2')
const router = require('./router')
const connfilter = require('./connfilter')
const http1 = require('./http1')
const httpt = require('./http2')

/**
 * @param {object} options 初始化选项，参考值如下：
 * - ignoreSlash，忽略末尾的/，默认为true
 * - maxBody body数据最大字节数。
 * - debug 调试模式，默认为false
 * - maxConn 最大连接数，使用daemon接口，则每个进程都可以最多处理maxConn限制数量，0表示不限制。
 * - deny  {Array} IP字符串数组，表示要拒绝访问的IP。
 * - maxIPRequest {number} 单个IP单元时间内最大访问次数。
 * - peerTime {number} 单元时间，配合maxIPRequest，默认为1表示1秒钟清空一次。
 * - maxIPCache {number} 最大IP缓存个数，配合限制IP访问次数使用，默认为15000。
 * - allow   {Array} 限制IP请求次数的白名单。
 * - useLimit {bool} 启用连接限制，用于限制请求的选项需要启用此选项才会生效。
 * - timeout {number} 超时。
 * - cert {string} 启用HTTPS要使用的证书文件路径。
 * - key  {string} 启用HTTPS的密钥文件路径。
 * - maxFiles {number} 最大上传文件数量，超过则不处理。
 * - daemon {bool} 启用守护进程模式。
 * - server {object}  服务器选项，参考http2.createSecureServer
 * - notFound {string} 404页面数据。
 * - parseBody {bool} 自动解析上传文件数据，默认为true。
 * - http2 {bool} 默认false。
 * - socktimeout 设置socket超时，默认sock是不超时的，
 *      这容易在网络不好的情况下，导致一些套接字被长期占有，默认值为0 和node一致
 * - requestHandle 在开始请求时，如果设置则会传递参数并执行，之后继续处理请求，
 *     传递参数参考http和http2模块的request事件。
 */
var doio = function (options = {}) {

  if (!(this instanceof doio)) {
    return new doio(options)
  }

  this.config = {
    //此配置表示POST/PUT提交表单的最大字节数，也是上传文件的最大限制，
    maxBody: 8000000,
    maxFiles: 12,
    daemon: false, //开启守护进程
    /*
      开启守护进程模式后，如果设置路径不为空字符串，则会把pid写入到此文件，可用于服务管理。
    */
    pidFile: '',

    //开启HTTPS
    https: false,

    http2: false,

    //HTTPS密钥和证书的路径
    key: '',
    cert: '',

    //服务器选项，参考http2.createSecureServer、tls.createServer
    server: {
      handshakeTimeout: 7168, //TLS握手连接（HANDSHAKE）超时
      //sessionTimeout: 350,
    },
    //设置服务器超时，毫秒单位，在具体的请求中，可以通过stream设置具体请求的超时时间。
    timeout: 18000,

    debug: false,

    ignoreSlash: true,

    useLimit: false,

    requestHandle: null,

    notFound: 'Not Found',
    badRequest: 'Bad Request',
  };

  this.whoami = 'doio'

  this.limit = {
    maxConn: 1024, //限制最大连接数，如果设置为0表示不限制
    deny: [], //拒绝请求的IP。
    maxIPRequest: 0, //每秒单个IP可以进行请求次数的上限，0表示不限制。
    peerTime: 1, //IP访问次数限制的时间单元，1表示每隔1秒钟检测一次。
    maxIPCache: 28000, //存储IP最大个数，是req_ip_table的上限，过高于性能有损。
    allow: [], //限制IP请求次数的白名单。
    timeout: 0
  }

  if (typeof options !== 'object') {
    options = {}
  }

  for (let k in options) {
    switch (k) {
      case 'maxConn':
        if (typeof options.maxConn == 'number' && parseInt(options.maxConn) >= 0) {
          this.limit.maxConn = options.maxConn
        }
        break

      case 'deny':
        this.limit.deny = options.deny
        break

      case 'maxIPRequest':
        if (parseInt(options.maxIPRequest) >= 0) {
          this.limit.maxIPRequest = parseInt(options.maxIPRequest)
        }
        break

      case 'peerTime':
        if (parseInt(options.peerTime) > 0) {
          this.limit.peerTime = parseInt(options.peerTime)
        }
        break

      case 'maxIPCache':
        if (parseInt(options.maxIPCache) >= 1024) {
          this.limit.maxIPCache = parseInt(options.maxIPCache)
        }
        break

      case 'allow':
        this.limit.allow = options.allow
        break

      case 'socktimeout':
        this.limit.timeout = options.socktimeout
        break

      case 'requestHandle':
        if (typeof options.requestHandle === 'function') {
          this.config.requestHandle = options.requestHandle
        }
        break

      case 'logType':
      case 'daemon':
      case 'maxFiles':
      case 'maxBody':
      case 'debug':
      case 'timeout':
      case 'ignoreSlash':
      case 'useLimit':
      case 'http2':
      case 'https':
      case 'notFound':
      case 'badRequest':
        this.config[k] = options[k]
        break

      default: ;
    }
  }

  if (options.server !== undefined && typeof options.server === 'object') {
    for (let x in options.server) {
      this.config.server[x] = options.server[x]
    }
  }

  if (options.key && options.cert) {
    this.config.cert = options.cert
    this.config.key = options.key
    this.config.https = true
  }

  /**
   * 记录当前的运行情况
   */
  this.rundata = {
    conn: 0,
    platform: os.platform(),
    host: '',
    port: 0,
    cpuLast: { user: 0, system: 0 },
    cpuTime: { user: 0, system: 0 },
    mem: {
      rss: 0,
      heapTotal: 0,
      heapUsed: 0,
      external: 0
    }
  }

  this.bodyparser = new bodyParser({ maxFiles: this.config.maxFiles })

  this.router = new router(this.config)

  if (this.config.http2) {
    this._midware = new middleware2(options)
  } else {
    this._midware = new middleware1(options)
  }

  this._midware.cache = true

  //this.use = this._midware.use.bind(this._midware);
  this.use = function (midcall, filter = null, group = null) {
    this._midware.use(midcall, filter, group)
    return this
  };

  //运行时服务，需要在全局添加一些服务插件可以放在此处。
  this.service = {}

  this.httpServ = null

  var opts = {
    config: this.config,
    events: this.eventTable,
    router: this.router,
    midware: this._midware,
    service: this.service,
    handle: this.config.requestHandle
  }

  if (this.config.http2) {
    this.httpServ = new httpt(opts)
  } else {
    this.httpServ = new http1(opts)
  }

  let mth = '';
  for (let k in this.router.apiTable) {
    mth = k.toLowerCase()
    this[mth] = this.router[mth].bind(this.router)
  }

  this.map = this.router.map.bind(this.router)

  this.any = this.router.any.bind(this.router)

};

/**
 * 绑定事件的暂存结构和方法
 */
doio.prototype.eventTable = {};
doio.prototype.on = function (evt, callback) {
  this.eventTable[evt] = callback
};

doio.prototype.addService = function (key, serv) {
  this.service[key] = serv
};

doio.prototype.hooks = []

doio.prototype.pre = function (midcall, filter = null, group = null) {
  if (typeof midcall == 'function' && midcall.constructor.name == 'AsyncFunction') {
    this.hooks.push({
      midcall: midcall,
      filter: filter,
      group: group
    })

  }

  return this
}

/** 
 * 根据配置情况确定运行HTTP/1.1还是HTTP/2
 * @param {number} port 端口号
 * @param {string} host IP地址，可以是IPv4或IPv6
 * 0.0.0.0 对应使用IPv6则是::
*/
doio.prototype.run = function (port = 2022, host = '0.0.0.0') {

  this.rundata.host = (typeof port == 'number' ? host : '')

  this.rundata.port = port

  if (this.router.count === 0) {
    this.router.any('/*', async c => {
      c.res.body = 'doio success';
    })

  }

  this._midware._loadstack()

  this._midware.cleanstack()

  this._midware.add(this.bodyparser.middleware())

  this._midware.add(this.httpServ.middleware())

  //add second hooks
  let m = null

  while ((m = this.hooks.pop()) !== undefined) {
    this._midware.add(m.midcall, m.filter, m.group)
  }

  //必须放在最后，用于返回最终数据。
  this._midware.addFinal()

  if (this.config.useLimit) {
    var connlimit = new connfilter(this.limit, this.rundata)
    this.on('connection', connlimit.callback)
  } else {
    this.on('connection', (sock) => {
      this.rundata.conn += 1

      sock.on('close', () => {
        this.rundata.conn -= 1
      })

    })

  }

  this.rundata.host = host

  this.rundata.port = port

  return this.httpServ.run(port, host)

}

/**
 * 提供一个守护进程消息事件监听机制，记录对应事件和函数，并进行回调处理，
 * 进而抽离出负载、日志等功能作为独立的模块。消息必须是JSON对象，type字段说明了事件类型。
 */
doio.prototype.daeMsgEvent = {
  '_eaddr': {
    count: 0,
    overflow: 0,
    mode: 'always',
    callback: (w, msg) => {
      let errmsg = '端口已被使用，请先停止正在运行的进程。\n(在Linux/Unix上，可通过'
        + 'ps -e -o user,pid,ppid,comm,args | grep node | grep -v node'
        + ' 或 ss -utlp 查看相关进程';
      console.error(errmsg);
      process.kill(0, 'SIGABRT');
    }
  }
}

doio.prototype.setMsgEvent = function (evt, callback, mode = 'always') {
  if (cluster.isWorker) {
    return false
  }

  if (typeof callback !== 'function') {
    console.error('callback not a function')
    return false
  }

  this.daeMsgEvent[evt] = {
    count: 0,
    mode: mode,
    overflow: 0,
    callback: callback
  }

  return true
}

doio.prototype.daemonMessage = function () {
  if (cluster.isWorker) {
    return
  }

  var self = this

  for (let k in this.daeMsgEvent) {
    this.daeMsgEvent[k].count = 0
    this.daeMsgEvent[k].overflow = 0
  }

  cluster.on('message', (worker, msg, handle) => {
    try {
      if (typeof msg === 'object' && msg.type !== undefined) {
        if (self.daeMsgEvent[msg.type] === undefined) {
          return
        }
        
        let devt = self.daeMsgEvent[msg.type]

        if (devt.mode === 'once' && devt.count > 0) {
          return
        }

        devt.count += 1

        if (devt.count <= 0) {
          devt.count = 0
          
          devt.overflow += 1

        }

        devt.callback(worker, msg)
      }

    } catch (err) {
      if (self.config.debug) {
        console.error(err)
      }
    }
  });
};

doio.prototype.printRoute = function () {
  if (cluster.isMaster) {
    this.setMsgEvent('print-route', (w, msg) => {
      console.log(msg.table)
      console.log(msg.listen)
      console.log(msg.protocol)
    }, 'once')

  } else {
    if (process.send && typeof process.send === 'function') {
      setTimeout(() => {

        process.send({
          type: 'print-route',
          table: this.router.getTable(),
          listen: `Host: ${this.rundata.host} ; Port: ${this.rundata.port}`,
          protocol: `HTTPS: ${this.config.https} ; HTTP/2: ${this.config.http2}`
        })

      }, 1)

    }
  }
}

/*
 * workers记录了在cluster模式，每个worker的启动时间，这可以在disconnect事件中检测。
 * */
doio.prototype.workers = {}

//如果worker运行在workerErrorTime时间内退出说明可能存在问题，这时候则终止master进程并立即给出错误信息。
doio.prototype.workerErrorTime = 980
doio.prototype.errorBreakCount = 0
doio.prototype.totalWorker = 0

/**
 * 这个函数是可以用于运维部署，此函数默认会根据CPU核数创建对应的子进程处理请求。
 * @param {number} port 端口号
 * @param {string} IP地址，IPv4或IPv6，如果检测为数字，则会把数字赋值给num。
 * @param {number} num，要创建的子进程数量，0表示自动，这时候根据CPU核心数量创建。
*/
doio.prototype.daemon = function (port = 2022, host = '0.0.0.0', num = 0) {
  if (typeof host === 'number') {
    num = host
    host = '0.0.0.0'
  }

  var self = this

  if (process.argv.indexOf('--daemon') > 0) {
  } else if (this.config.daemon) {
    
    var args = process.argv.slice(1)

    args.push('--daemon')

    const serv = spawn(process.argv[0], args, { detached: true, stdio: ['ignore', 1, 2] })

    serv.unref()

    process.exit(0)

  }

  if (cluster.isMaster) {
    let osCPUS = os.cpus().length

    if (num > (osCPUS * 2)) {
      num = 0
    }

    if (num <= 0) {
      num = osCPUS
      //如果CPU核心数超过2个，则使用核心数-1的子进程处理请求.
      if (num > 2) {
        num -= 1
      }
    }

    this.totalWorker = num
    this.rundata.port = port
    this.rundata.host = (typeof port === 'number' ? host : '')

    if (typeof this.config.pidFile === 'string' && this.config.pidFile.length > 0) {
      fs.writeFile(this.config.pidFile, process.pid, (err) => {
        if (err) {
          console.error(err)
        }
      })
    }

    this.daemonMessage()

    //clear router and service
    this.service = {}
    this.router.clear()
    this._midware.cleanstack()

    cluster.on('listening', (worker, addr) => {
      worker.process.on('error', err => {
        if (self.config.debug) console.error('--DEBUG-WORKER-ERR:', err)
      })

      this.workers[worker.id] = {
        startTime: Date.now(),
        address: addr,
        id: worker.id,
        pid: worker.process.pid,
        conn: 0,
        mem: {
          rss: 0,
          heapTotal: 0,
          heapUsed: 0
        },
        cpu: { user: 0, system: 0 }
      }
      
    })

    let exitTip = () => {
      setTimeout(() => {
        let errmsg = `worker进程在限制最短时间内(${this.workerErrorTime}ms)退出，请检测代码是否存在错误。`

        console.error(errmsg)

        process.kill(0, 'SIGABRT')

      }, 12)
    }

    cluster.on('exit', (worker, code, signal) => {
      let w = this.workers[worker.id]
      if (w) {
        let tm = Date.now();
        if (tm - w.startTime <= this.workerErrorTime && this.errorBreakCount <= 0) {
          exitTip()
        } else {
          delete this.workers[w.id]
        }
        this.errorBreakCount = 1
      } else {
        exitTip()
      }
    });

    process.on('SIGABRT', (sig) => {
      process.exit(1)
    });

    process.on('SIGCHLD', (sig) => {
      let num_dis = num - Object.keys(cluster.workers).length
      if (num_dis <= 0) return

      for (let i = 0; i < num_dis; i++)
        cluster.fork()
    })

    for (let i = 0; i < num; i++) {
      cluster.fork()
    }

  } else if (cluster.isWorker) {
    this.run(port, host)
  }
}

module.exports = doio
